1

场景介绍

我们考虑简单的客户端——服务器通信的场景,其典型模式为:

  • 服务端通过close()主动关闭一个TCP连接

  • 客户端通过read()获得了0(表示服务端没有数据),调用close()关闭这个连接。

在TCP层面表现为:

  • 服务器调用close()后,向客户端发送FIN,客户端回应FIN-ACK。服务器进入FIN-WAIT-2状态,客户端进入CLOSE-WAIT状态。

  • 客户端调用close()后,向服务端发送FIN,服务端会用FIN-ACK。服务端进入TIME-WAIT状态,客户端直接进入CLOSE状态,连接结束。

我们考虑一些异常情况,客户端上:

  • 客户端在获得read()==0后,没有及时地调用close()调用;

  • 客户端在获得read()==0后,仍然向服务端写入数据。

测试程序

选用《UNIX网络编程》(第一卷)中的服务器——客户端样例程序。服务器部分:


    int main(int argc, char **argv)
    {
        int                    listenfd, connfd;
        socklen_t            len;
        struct sockaddr_in    servaddr, cliaddr;
        char                buff[MAXLINE];
        time_t                ticks;
    
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family      = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port        = htons(9999);    /* daytime server */
    
        Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
        Listen(listenfd, LISTENQ);
    
        for ( ; ; ) {
            len = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *) &cliaddr, &len);
            printf("connection from %s, port %d\n",
                   Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
                   ntohs(cliaddr.sin_port));
    
            ticks = time(NULL);
            snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
            Write(connfd, buff, strlen(buff));
    
            Close(connfd);
        }
    }

服务器监听9999端口,新链接建立后,发出当前时间,随后调用close()关闭连接。

客户端部分:

    int main(int argc, char **argv)
    {
        int                    sockfd, n;
        socklen_t            len;
        char                recvline[MAXLINE + 1];
        struct sockaddr_in    servaddr, cliaddr;
    
        if (argc != 2)
            err_quit("usage: a.out <IPaddress>");
    
        if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            err_sys("socket error");
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port   = htons(9999);    /* daytime server */
        if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
            err_quit("inet_pton error for %s", argv[1]);
    
        if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
            err_sys("connect error");
    
        len = sizeof(cliaddr);
        Getsockname(sockfd, (SA *) &cliaddr, &len);
        printf("local addr: %s\n",
               Sock_ntop((SA *) &cliaddr, sizeof(cliaddr)));
    
        while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
            recvline[n] = 0;    /* null terminate */
            if (fputs(recvline, stdout) == EOF)
                err_sys("fputs error");
        }
    
        if (n < 0)
            err_sys("read error");
    
        exit(0);
    }

客户端没有及时地调用close()调用

我们修改客户端调用close()的处理流程。当read()==0时,阻塞睡眠。等待一段时间后,发送SIGINT使进程退出,相当于调用close()。注意到以下的现象:

  • 服务端SOCKET处于FIN-WAIT-2状态时,发送SIGINT信号使客户端退出,客户端发送FIN,服务端回复FIN-ACK。此时,按正常流程结束链接。

  • 服务端SOCKET等待FIN-WAIT-2状态超时后,客户端发送FIN,服务端回复RST结束链接。

Orphan Socket: 从应用程序来看,此条socket连接已经收发数据完毕,关闭了此连接,但是linux内核中为了完成正常的tcp协议(比如缓冲区中的数据)转换,会在内核的tcp协议层继续维护这些sock状态,直至系统回收。处于此种状态下的socket就是orphan socket。

分析:

  • 服务端SOCKET关闭后,没有对这一SOCKET的引用。这一SOCKET进入到“孤儿SOCKET“的状态。孤儿Socket存在时,系统协议栈负责完成后续的FIN流程。当孤儿Socket超时后,系统协议栈将不存在这一Socket的信息。客户端此时发送FIN,将收到RST应答。


luqiuwen
34 声望0 粉丝